using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using System;

namespace DotNet.Common
{
    public class AsynIDQueue
    {
        int _id;
        int _prepareID;
        Dictionary<int, AutoResetEvent> _asynIDEventDic;
        bool _stopFlag;

        public AsynIDQueue()
        {
            _asynIDEventDic = new Dictionary<int, AutoResetEvent>();

            Start();
        }

        ~AsynIDQueue()
        {
            Stop();
        }

        public void Start()
        {
            _stopFlag = false;
        }

        public void Stop()
        {
            _stopFlag = true;
            _asynIDEventDic.Clear();
            lock (this)
            {
                _id = 0;
                _prepareID = 0;
            }
        }

        public int NewID()
        {
            lock (this)
            {
                return _id++;
            }
        }

        public void Enter(int id)
        {
            if (_stopFlag)
                return;

            if (id != _prepareID)
            {
                AutoResetEvent waiter = new AutoResetEvent(false);
                _asynIDEventDic.Add(id, waiter);
                waiter.WaitOne();
            }
        }

        public void Exit(int id)
        {
            if (id != _prepareID)
                return;

            _asynIDEventDic.Remove(id);

            ++_prepareID;
            Set(++id);
        }

        public void Set(int id)
        {
            AutoResetEvent waitEvent;
            if (_asynIDEventDic.TryGetValue(id, out waitEvent))
            {
                waitEvent.Set();
            }
        }
    }

    public interface IAsynIDLock
    {
        void Enter();
        void Exit();
    }

    public class AsynID : IAsynIDLock, IDisposable
    {
        bool enterFlag;
        int _id;
        AsynIDQueue _queue;

        public AsynID(AsynIDQueue queue)
        {
            _queue = queue;
            _id = queue.NewID();
            enterFlag = false;
        }

        ~AsynID()
        {
            Dispose();
        }

        public void Enter()
        {
            if (enterFlag)
                return;

            enterFlag = true;
            _queue.Enter(_id);
        }

        public void Exit()
        {
            _queue.Exit(_id);
            enterFlag = false;
        }

        public void Dispose()
        {
            if (enterFlag)
            {
                _queue.Set(_id);
            }
        }
    }
}